/**
 * 
 */
package com.zte.claa.inficombo.csa.app.service.rptparse;

import java.io.Serializable;
import java.nio.ByteBuffer;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 0283000115
 *
 */

@SuppressWarnings("serial")
public class MultiPackageData implements Serializable {
	public static final byte CONFIRMED_YES = 1;
	public static final byte CONFIRMED_NO = 0;
	
	public static final byte PARSED_YES = 1;
	public static final byte PARSED_NO = 0;
	
	public static final int INVALID_MSGID = -1;
	
	// 分包模式.
	public static final byte MP_MODE_NORMAL = 0;
	public static final byte MP_MODE_BYTE = 1;
	// 针对GSP设备的交叉多包（sensorA和B交叉上报分包数据)
	public static final byte MP_GSP_CROSSMPK = 2;
	
	// 对应的deveui.
	private String deveui;
	private String devName;
	private String projectid;
	private String appeui;
	private String devtype;
	private String portStr;
	private long rptTime = 0;
	// js内部使用消息。
	private int msgtp = 0;
	private String msgver = "";
	// 消息大包序号
	private int msgid = INVALID_MSGID;
	
	// 预计总包数
	private int totalNum = 0;
	// 是否已经下发确认.
	private byte isConfirmed = CONFIRMED_NO;
	// 数据完整后是否做了metedata解析标记
	private byte isParsed = PARSED_NO;
	
	/**
	 * 数据包原始映射表. key: 包序号，
	 *    对于普通分包模式，要求从1开始；
	 *    对于按字节的动态分包模式，可以不连续，通常为起始字节的序号，如0,10,50,89,120，.... 
	 * val：包原始码流。
	 */
	private Map<Integer, byte[]> dataMap = null;
	
	/**
	 * GSP多子卡交叉多包的场景下，分sensorid存储各自的分包数据.
	 * 注：
	 * 1.为了方便单包和交叉多包两个mpd的merge，要求单包数据上报时，同时保存到如下这个map中；
	 * 2.当前是单包模式：如果是1包上报数据，则设置totalnum=1，数据存两份；
	 *               如果是多包上报数据，则设置totalnum=10000，数据存一份；
	 * 3.当前是多包模式：无论单包上报还是多包上报，都不设置totalnum，数据存一份；
	 */
	private Map<String, GspSubCrossMPKData> crossSubMpkMap = null;
	
	// 需要扩展配置的类型，对应的扩展配置数据. 主要针对自研设备的模型附加数据信息.
	private Map<String, Object> devExtDatas = null;
	
	/**
	 * 为支持 蚌埠日月的油井设备（按字节动态分包），增加如下参数：
	 * 1、分包模式；0--正常分包；1--按字节动态分包；2--GSP交叉多包：
	 * 2、总字节数：totalByteNum；
	 */
	private byte multiPkgMode = MP_MODE_NORMAL;
	private int totalByteNum = 0;
	
	/**
	 * 构建器.
	 */
	public MultiPackageData(){
		this.dataMap = new ConcurrentHashMap<Integer, byte[]>();
		this.crossSubMpkMap = new ConcurrentHashMap<String, GspSubCrossMPKData>(0);
		this.rptTime = System.currentTimeMillis();
	}

	public String getDeveui() {
		return deveui;
	}

	public void setDeveui(String deveui) {
		this.deveui = deveui;
	}

	public String getDevName() {
		return devName;
	}

	public void setDevName(String devName) {
		this.devName = devName;
	}

	public String getDevtype() {
		return devtype;
	}

	public void setDevtype(String devtype) {
		this.devtype = devtype;
	}

	public int getTotalNum() {
		return totalNum;
	}

	public void setTotalNum(int totalNum) {
		this.totalNum = totalNum;
	}

	public int getCurNum() {
		return this.dataMap.size();
	}

	public byte getIsConfirmed() {
		return isConfirmed;
	}

	public void setIsConfirmed(byte isConfirmed) {
		this.isConfirmed = isConfirmed;
	}
	
	public byte getIsParsed() {
		return isParsed;
	}

	public void setIsParsed(byte isParsed) {
		this.isParsed = isParsed;
	}

	public String getPortStr() {
		return portStr;
	}

	public void setPortStr(String portStr) {
		this.portStr = portStr;
	}

	/**
	 * 添加数据（普通分包模式）。
	 * 
	 * @param pkgNo
	 * @param data
	 * @return： 1--数据包重复；
	 *          0--数据包不重复；
	 */
	public int appendData(int pkgNo, byte[] data) {
		int ret = 0;
		if(this.dataMap.get(pkgNo) != null){
			ret = 1;
		}
		this.dataMap.put(pkgNo, data);
		return ret;
	}
	
	/**
	 * 添加数据（按字节分包模式）。
	 * 
	 * 根据消息id进行容错处理。
	 * 
	 * @param pkgNo （大包内的小包序号）
	 * @param curmsgid （当前大包序号）
	 * @param data
	 * @return： 1--数据包不合法；
	 *          0--数据包合法；
	 */
	public int appendData(int pkgNo, int curmsgid, byte[] data) {
		synchronized (this) {
			if (this.msgid == INVALID_MSGID || this.msgid == curmsgid) {
				this.msgid = curmsgid;
				this.dataMap.put(pkgNo, data);
				return 0;
			}
			return 1;
		}
	}
	
	/**
	 * 批量设置数据（普通分包模式）.
	 * @param dMap
	 */
	public void appendAllData(Map<Integer, byte[]> dMap){
		if(dMap != null){
			this.dataMap.putAll(dMap);
		}
	}
	
	/**
	 * 添加数据（GSP-传感器分包数据上报）。
	 * GSP平台终端统一按交叉多包模式处理.
	 * 
	 * JS插件调用方式：(针对GSP-JS插件)
	 * A. 如果是GSP设备数据上报（无论单包或多包）：
	 *    1）首先调用 setMpModeOfGspCross()；
	 *    2）其次调用这里的appendData();
	 * B. 如果是非GSP设备类型，则不要调用这里的方法appendData()；
	 * 
	 * @param pkgNo
	 * @param data  --->【要求】：按pkgno依次合并后，能够直接交给对应传感器类型的JS插件解析的数据。
	 * 
	 * @return： 1--数据包重复；
	 *          0--数据包不重复；
	 */
	public int appendData(String sensorId, int msgId, int totalPkNum, int pkgNo, byte[] data) {
		int rc = 0;
		// 为了交叉多包和普通模式下的merge操作，这里同步写入交叉多包的映射表（基本上非GSP设备不会调到这里）.
		GspSubCrossMPKData cscmpk = this.crossSubMpkMap.get(sensorId);
		if(cscmpk == null){
			cscmpk = new GspSubCrossMPKData();
			cscmpk.setRptTime(System.currentTimeMillis());
			cscmpk.setSensorId(sensorId);
			cscmpk.setMsgid(msgId);
			cscmpk.setTotalNum(totalPkNum);
			cscmpk.appendData(pkgNo, data);
			this.crossSubMpkMap.put(sensorId, cscmpk);
			return rc;
		} else {
			cscmpk.setMsgid(msgId);
			cscmpk.setTotalNum(totalPkNum);
			int tmprc = cscmpk.appendData(pkgNo, data);
			if( tmprc == 1 && this.isGspCrossMpkMode()){
				rc = 1;
			}
		}
		return rc;
	}
	
	// 获取指定sensor的多包数据.
	public GspSubCrossMPKData getGspSubCrossMPKData(String sensorId){
		return this.crossSubMpkMap.get(sensorId);
	}
	
	public void setGspSubCrossMPKData(String sensorId, GspSubCrossMPKData data){
		this.crossSubMpkMap.put(sensorId, data);
	}
	
	public void removeGspSubCrossMPKData(String sensorId){
		this.crossSubMpkMap.remove(sensorId);
	}
	
	public int getCurrentCrossMPKDataNum(){
		return this.crossSubMpkMap.size();
	}
	
	// 获取所有keys（sensorids）
	public List<String> getAllGspSubCrossMpkMapKeys(){
		List<String> keyList = new ArrayList<String>();
		keyList.addAll(this.crossSubMpkMap.keySet());
		return keyList;
	}
	
	// 这里仅处理非GSP交叉多包场景.
	public boolean isDataOk(){
		// 按字节动态分包模式...
		if(this.multiPkgMode == MP_MODE_BYTE){
			return this.totalByteNum <= this.getCurDataLen();
		} 
		// 普通分包模式.
		return this.dataMap.size() == this.totalNum;
	}
	
	public String getProjectid() {
		return projectid;
	}

	public void setProjectid(String projectid) {
		this.projectid = projectid;
	}

	public String getAppeui() {
		return appeui;
	}

	public void setAppeui(String appeui) {
		this.appeui = appeui;
	}

	public Map<Integer, byte[]> getDataMap() {
		return dataMap;
	}

	public long getRptTime() {
		return rptTime;
	}

	public void setRptTime(long rptTime) {
		this.rptTime = rptTime;
	}
	
	public int getMsgtp() {
		return msgtp;
	}

	public void setMsgtp(int msgtp) {
		this.msgtp = msgtp;
	}

	/**
     * @return the msgver
     */
    public String getMsgver() {
        return msgver;
    }
    
    public int getMsgid() {
		return msgid;
	}

	public void setMsgid(int msgid) {
		this.msgid = msgid;
	}

	/**
     * @param msgver the msgver to set
     */
    public void setMsgver(String msgver) {
        this.msgver = msgver;
    }
    
    public Map<String, Object> getDevExtDatas() {
		return devExtDatas;
	}

	public void setDevExtDatas(Map<String, Object> devExtDatas) {
		this.devExtDatas = devExtDatas;
	}
	
	public Object getDevExtData(String dataName){
		if(this.devExtDatas == null){
			return null;
		}
		return this.devExtDatas.get(dataName);
	}

	public byte getMultiPkgMode() {
		return multiPkgMode;
	}

	// 注：单包时不需要设置，也不要设置，以免影响当前可能已经存在的交叉多包的情况：比如 B1，A1，等待 B2...
	public void setMultiPkgMode(byte multiPkgMode) {
		this.multiPkgMode = multiPkgMode;
		if(this.multiPkgMode == MP_GSP_CROSSMPK){
			this.dataMap.clear();
			this.totalNum = 0;
		} else {
			this.crossSubMpkMap.clear();
		}
	}

	public int getTotalByteNum() {
		return totalByteNum;
	}

	public void setTotalByteNum(int totalByteNum) {
		this.totalByteNum = totalByteNum;
	}
	
	// 设置为按字节动态分包.
	public void setMpModeOfByte(){
		this.multiPkgMode = MP_MODE_BYTE;
	}
	
	public void setMpModeOfGspCross(){
		this.multiPkgMode = MP_GSP_CROSSMPK;
		this.dataMap.clear();
		this.totalNum = 0;
	}
	
	// 判断当前mpd的模式是否为GSP交叉多包模式...
	public boolean isGspCrossMpkMode(){
		return this.multiPkgMode == MP_GSP_CROSSMPK;
	}	

	// 重新初始化mpd对象》
	public void reInit(){
		this.dataMap.clear();
		this.crossSubMpkMap.clear();
		this.totalNum = 0;
		this.isConfirmed = CONFIRMED_NO;
		this.isParsed = PARSED_NO;
		this.msgid = INVALID_MSGID;
		this.rptTime = System.currentTimeMillis();
		this.multiPkgMode = MP_MODE_NORMAL;
		this.totalByteNum = 0;
		if(this.devExtDatas != null){
			this.devExtDatas.clear();
		}
		this.devExtDatas = null;
	}
	
	/**
	 * 拼接数据流，返回完整结果.
	 * @return
	 */
	public byte[] buildData() {
		if(this.multiPkgMode == MP_MODE_BYTE) {
			return this.buildData4MpByteMode();
		}
		int totalLen = 0;
		for(int i=1; i<=this.totalNum; i++){
			byte[] bs = this.dataMap.get(i);
			if(bs == null){
				continue;
			}
			totalLen += bs.length;
		}		
		if(totalLen == 0){
			return new byte[0];
		}
		ByteBuffer bb = ByteBuffer.allocate(totalLen);
		for(int i=1; i<=this.totalNum; i++){
			byte[] bs = this.dataMap.get(i);
			if(bs == null){
				continue;
			}
			bb.put(bs);
		}	
		return bb.array();
	}
	
	// 获取当前所收数据的总字节数.
	private byte[] buildData4MpByteMode(){
		int totalLen = 0;
		Iterator<Integer> it = this.dataMap.keySet().iterator();
		while(it.hasNext()){
			Integer key = it.next();
			byte[] bs = this.dataMap.get(key);
			if(bs == null){
				continue;
			}
			totalLen += bs.length;
		}		
		if(totalLen == 0){
			return new byte[0];
		}
		// 按key的值从小到大顺序依次读取byte[]...  
		ByteBuffer bb = ByteBuffer.allocate(totalLen);
		List<Integer> keyList = new ArrayList<Integer>();
		keyList.addAll(this.dataMap.keySet());		
		// a - b 升序.
		keyList.sort((a, b) -> (a.compareTo(b)));
		int len = keyList.size();
		for(int i=0; i<len; i++){
			byte[] bs = this.dataMap.get(keyList.get(i));
			if(bs == null){
				continue;
			}
			bb.put(bs);
		}	
		return bb.array();
	}
	
	/**
	 * 返回收包位图（bitmap），从左到右边顺序，对应第1~N个包：0-未收到，1-收到。
	 * 
	 * 注：该方法仅适用于普通分包模式！！
	 * 
	 * 如： 1101，表示第3个包未收到。
	 * @param ignoreAbsentPkg = true表示忽略丢包.
	 * @return
	 */
	public int getBitmap(boolean ignoreAbsentPkg){
		int bmVal = 0;
		for(int i=1; i<=this.totalNum; i++){
			byte[] bs = this.dataMap.get(i);
			if(bs == null && ignoreAbsentPkg == false){
				bmVal = (bmVal << 1) | 0;
			} else {
				bmVal = (bmVal << 1) | 1;
			}
		}
		return bmVal;
	}
	
	// 获取当前所收数据的总字节数.
	public int getCurDataLen(){
		int totalLen = 0;
		Iterator<Integer> it = this.dataMap.keySet().iterator();
		while(it.hasNext()){
			Integer key = it.next();
			byte[] bs = this.dataMap.get(key);
			if(bs == null){
				continue;
			}
			totalLen += bs.length;
		}	
		return totalLen;
	}

	public String toString(){
		StringBuffer sb = new StringBuffer();
		sb.append("deveui=").append(deveui).append(",");
		sb.append("rptTime=").append(new Date(rptTime).toString()).append(",");
		sb.append("projectid=").append(projectid).append(",");
		sb.append("appeui=").append(appeui).append(",");
		sb.append("devtype=").append(devtype).append(",");
		sb.append("msgid=").append(msgid).append(",");
		sb.append("msgtp=").append(msgtp).append(",");
		sb.append("isConfirmed=").append(isConfirmed).append(",");
		sb.append("totalNum=").append(totalNum).append(",");
		sb.append("multiPkgMode=").append(this.multiPkgMode).append(",");
		sb.append("totalByteNum=").append(this.totalByteNum).append(",");
		sb.append("curPkgNum=").append(this.dataMap.size()).append(": ");		
		Iterator<Integer> it = this.dataMap.keySet().iterator();
		while(it.hasNext()){
			Integer itKey = it.next();
			byte[] bs = this.dataMap.get(itKey);
			int len = (bs == null) ? 0 : bs.length;
			sb.append("key[").append(itKey).append("].datalen=[").append(len).append("]; ");
		}
		return sb.toString();		
	}
	
	// 专门打印crossmpk数据...
	public String printCrossMpkData(){
		StringBuffer sb = new StringBuffer();
		sb.append("deveui=").append(deveui).append(",");
		sb.append("rptTime=").append(new Date(rptTime).toString()).append(",");
		sb.append("projectid=").append(projectid).append(",");
		sb.append("multiPkgMode=").append(this.multiPkgMode).append(": ");	
		Iterator<String> it = this.crossSubMpkMap.keySet().iterator();
		while(it.hasNext()){
			String itKey = it.next();
			GspSubCrossMPKData data = this.crossSubMpkMap.get(itKey);
			sb.append("key[").append(itKey).append("]: ").append(data).append("; ");
		}
		return sb.toString();	
	}
}
